package com.hbnu.service;
import com.hbnu.client.FileClient;
import com.hbnu.entity.FileStat;
import com.hbnu.enums.Status;
import com.hbnu.message.GetPath;
import com.hbnu.message.FileInfo;
import com.hbnu.tools.FileTool;
import com.hbnu.tools.TriConsumer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelPromise;
import io.netty.channel.DefaultFileRegion;
import io.netty.channel.FileRegion;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.Promise;

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

import static com.hbnu.client.FileClient.promise;
import static com.hbnu.tools.PrintTool.print;
import static com.hbnu.tools.PrintTool.println;


public class FileService {
    //处理结果
    public static final AttributeKey<ChannelPromise> PROMISE_KEY = AttributeKey.valueOf("promise");
    private final ExecutorService executorService; // 线程池
    private final FileClient fileClient;
    private final ScheduledExecutorService scheduler;//
    public FileService(){
        scheduler = Executors.newScheduledThreadPool(1);
        fileClient = new FileClient();
        executorService= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }
    private String ip= Status.host;

    public void setIp(String ip) {
        this.ip = ip;
    }

    //直接一直在后台
    public void close(){
        scheduler.shutdown();
        executorService.shutdown();
        // 关闭客户端  异步关闭等
        fileClient.stop();
        //System.out.println("好了！");
    }
    private Channel getChannel()  {
        try {
            return  fileClient.getBootstrap().connect(ip, Status.port).sync().channel();
        } catch (Exception e) {
            throw new RuntimeException("文件服务器无法连接...");
        }
    }

    /**
     *
     * @param name 文件名
     * @param localPath 本地地址
     * @param serverPath 服务器地址
     * @param hasFile 是否有文件
     * @param size 大小
     * @param consumer 对应功能
     * @return 是否成功
     */
    private boolean chunkFile(String name,String localPath,String serverPath, boolean hasFile,long size, TriConsumer<FileInfo,AtomicLong,Long> consumer)  {
        //boolean ok=true;
        AtomicBoolean ok=new AtomicBoolean(true);
        AtomicLong atomicLong = new AtomicLong();
        long time1 = System.currentTimeMillis();
        int chunkCount = (int) Math.ceil((double) size / Status.CHUNK_SIZE);
        List<Future<?>> futureList=new ArrayList<>();
        for (int i = 0; i < chunkCount; i++) {
            long start = (long) i * Status.CHUNK_SIZE;
            int length = (int) Math.min(size - start, Status.CHUNK_SIZE);
            FileInfo fileInfo = new FileInfo(hasFile, start, length, localPath ,serverPath);
            futureList.add(executorService.submit(() -> consumer.accept(fileInfo,atomicLong,size)));
        }
        //速度打印
        String str=hasFile?"上传中":"下载中";
        AtomicLong read= new AtomicLong();
        ScheduledFuture<?> scheduledFuture = scheduler.scheduleAtFixedRate(() -> {
            long len = atomicLong.get();
            //下载完成！ 或 异常 则不显示
            if ((len == size) || !ok.get()) {
                return;
            }
            long r = read.get();//上次长度
            System.out.printf(str + "(%s/s)......%.2f%%\r", FileTool.formatFileSize(len - r), len / (double) size * 100);
            //更新数值
            read.set(len);
        }, 0, 1, TimeUnit.SECONDS);
        //这里可以处理分片异常 将所有异常打印
        /*
            下载：读立马捕获异常
            上传：写不知道是否断开
            写操作首先写入本地TCP发送缓冲区
            只要缓冲区未满，write操作会成功返回
            实际数据是否成功发送到对端，write操作并不关心
         */
        for (Future<?> future : futureList) {
            try {
                // 服务器中断，设置超时时间5秒
               // future.get(5, TimeUnit.SECONDS);// 调用 get() 检查是否有异常
                future.get();// 调用 get() 检查是否有异常
            } catch (Exception e) {
                ok.set(false);
                future.cancel(true);
                print("\r请求超时！");
            }
        }
        //取消消定时任务 E:/视频/result/sql
        scheduledFuture.cancel(false);
        //异常情况 直接关闭
        if(!ok.get()) {
            //去除所有未执行任务
            for (Future<?> future : futureList) {
                if (!future.isDone()) {
                    future.cancel(true);
                }
            }
            println("文件服务器无法连接...");
            return false;
        }
        long time2 = System.currentTimeMillis();
        String s=hasFile?"上传完成!":"下载完成!";
        //改成大小 时间
        print(name+"\t"+s+"\t100.00%\t耗时");
        System.out.printf("%.2fs\n",(time2 - time1) / (double) 1000);
        return true;
    }


    //下载接口
    public  boolean download(FileStat fileStat, String savePath) {
        return chunkFile(fileStat.getName(),savePath,fileStat.getPath(),false,fileStat.getSize(),this::requestDownload);
    }
    private void requestDownload(FileInfo fileInfo,AtomicLong atomicLong,Long size) {
        try {
            // 连接服务器
            Channel channel = getChannel();
            ChannelPromise promise = channel.newPromise();
            channel.attr(PROMISE_KEY).set(promise);
            // 创建一个 ChannelPromise
            channel.writeAndFlush(fileInfo);
            promise.sync();
            //
            // 在客户端处理完后关闭channel，此时返回该线程（完成了一次下载请求）
            channel.closeFuture().sync();
            //进度更新
            atomicLong.addAndGet(fileInfo.getFileSize());
        } catch (InterruptedException e) {
            throw new RuntimeException("文件下载异常...");
        }
    }

    public boolean upload(File file,String serverPath) {
        return chunkFile(file.getName(),file.getAbsolutePath(),serverPath,true,file.length(),this::requestUpload);
    }
//E:/视频/result/qq123.mp4
    private void requestUpload(FileInfo fileInfo,AtomicLong atomicLong,long size)  {
        try {
            Channel channel = getChannel();
            ChannelPromise promise = channel.newPromise();
            channel.attr(PROMISE_KEY).set(promise);

            RandomAccessFile raf=new RandomAccessFile(fileInfo.getcPath(),"r");
            raf.seek(fileInfo.getStart());
            FileChannel fileChannel = raf.getChannel();

            //发送文件描述
            channel.writeAndFlush(fileInfo);
            //直接零拷贝写入
            FileRegion region = new DefaultFileRegion(fileChannel, fileInfo.getStart(), fileInfo.getFileSize());
            channel.writeAndFlush(region);

            promise.sync();

            // 阻塞直到获取请求结果
            channel.closeFuture().sync();
            atomicLong.addAndGet(fileInfo.getFileSize());
            // System.out.println(channel+"关闭了");
            raf.close();
            fileChannel.close();
        } catch (Exception e) {
            throw new RuntimeException("文件上传异常...");
        }
    }


    public void initPath() {
        try {
            Channel channel = getChannel();
            GetPath getPath = new GetPath();
            getPath.setIp(FileTool.getIP());
            channel.writeAndFlush(getPath).sync();

            channel.closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException("获取地址异常...");
        }
    }
}
//E:/视频/DJI_20241028195835_0071_D.MP4
//E:/桌面/文华学院/实习文案.docx
//E:/桌面/刀/nio/Netty教程源码资料/讲义/Netty-讲义/img/0007.png